/**
 * RmiUtils.java
 */
package fss.util.rmi;

import fss.client.ClientUIModel;
import fss.client.FssCommandLineClient;

import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.logging.Logger;

import fss.client.FssClient;
import fss.core.config.FssConfigFactory;
import fss.core.config.ServiceRegistryConfig;
import fss.remote.FssRegistry;
import fss.remote.FssServer;
import fss.remote.RemoteClient;
import fss.remote.RemoteService;
import fss.remote.ServiceRegistry;

/**
 * Common RMI utility methods.
 * 
 * @author thinh
 *
 */
public class RmiUtils {
    /**
     * The logger for this class.
     */
    private final static Logger logger = Logger.getLogger(RmiUtils.class.toString());
    /**
     * May need to be refactored.
     * @param host
     * @param port
     * @return
     * @throws RemoteException
     */
    public static HashMap<String, String> listRmiRegistryRemoteObjects(String host, int port) throws RemoteException {
        HashMap<String, String> ro = new HashMap<String, String>();
        Registry rmi = null;
        
        try {
            rmi = LocateRegistry.getRegistry(host, port);
            String[] names = rmi.list();
            for(String n : names) {
                try {
                    Remote o = rmi.lookup(n);
                    ro.put(n, o.getClass().getName());
                } catch (NotBoundException e) {
                    ro.put(n, "Remote object not found");
                }
            }
        } catch (RemoteException e) {
            throw e;
        }
        return ro;
    }
    /**
     * Bind a Remote Service to the specified RMI Registry as defined
     * by its RMI identifier.
     * @param service the remote service.
     * @throws RemoteException error binding the object.
     * @deprecated DO NOT USE THIS METHOD. TESTING ONLY!
     */
    public static void bindRemoteService(RemoteService service) throws RemoteException {
        RmiId id = service.getId();
        Registry rmi = LocateRegistry.getRegistry(id.getHost(), id.getPort());
        try {
            rmi.bind(id.getName(), service);
            logger.info("Remote Service binded " + id.getRmiUrl());
        } catch (AlreadyBoundException abe) {
            rmi.rebind(id.getName(), service);
            logger.info("Remote Service rebinded " + id.getRmiUrl());
        }
    }
    /**
     * Get a reference to a remote object.
     * <p>
     * Casting may be necessary if the caller knows the expected returned Remote object.
     * @param id the remote id.
     * @return the <code>Remote</code> object; or null if there were problems obtaining the reference.
     */
    public static Remote getRemoteObject(RmiId id) {
        Remote stub = null;
        try {
            stub = Naming.lookup(id.getRmiUrl());
        } catch (Exception e) {
            logger.warning("Unable to obtain remote object at " + id);
        }
        return stub;
    }
    /**
     * Checks the config file for the dedicated service registry ports and return the 
     * first available port on the specified host.
     * @param host the host.
     * @return an available dedicated port for the service registry.
     */
    public static Integer getAvailableServiceRegistryPort(String host) {
        ServiceRegistryConfig config = FssConfigFactory.getInstance().getRegistryServerConfig();
        for(int p : config.getPortList()) {
            try {
                Registry registry = LocateRegistry.getRegistry(host,p); // attempt to get the registry
                logger.info("Port " + p + " has " + registry.list().length + " objects.");
            } catch (RemoteException e) {
                logger.info("Got available registry on port " + p);
                return p;
            }
        }
        return null;
    }
    /**
     * Export an <code>FssRegistry</code> object to RMI.
     * @param impl the concrete implementation.
     * @return the RMI registry.
     * @throws RemoteException error exporting object.
     */
    public static Registry exportFssRegistry(FssRegistry impl) throws RemoteException {
        RmiId id = impl.getId();
        int port = id.getPort();
        String name = id.getName();
        ServiceRegistry stub = (ServiceRegistry) UnicastRemoteObject.exportObject(impl, port);
        Registry registry = LocateRegistry.createRegistry(port);
        try {            
            try {
                UnicastRemoteObject.unexportObject(stub, true);
            } catch (NoSuchObjectException oe) {
                // ignore
            }
            registry.bind(name, stub);
        } catch (AlreadyBoundException e) {
            registry.rebind(name, stub);
            logger.info("A rebind took place on remote object " + id);
        } 
        return registry;
    }
    /**
     * Export an <code>FssServer</code> object to RMI.
     * @param impl the concrete implementation.
     * @return the RMI registry.
     * @throws RemoteException error exporting object.
     */
    public static Registry exportFssServer(FssServer impl) throws RemoteException {
        RmiId id = impl.getId();
        int port = id.getPort();
        String name = id.getName();
        RemoteService stub = (RemoteService) UnicastRemoteObject.exportObject(impl, port);
        Registry registry = LocateRegistry.createRegistry(port);
        try {            
            try {
                UnicastRemoteObject.unexportObject(stub, true);
            } catch (NoSuchObjectException oe) {
                // ignore
            }
            registry.bind(name, stub);
        } catch (AlreadyBoundException e) {
            registry.rebind(name, stub);
            logger.info("A rebind took place on remote object " + id);
        } 
        return registry;
    }
    /**
     * Export an <code>FssClient</code> object to RMI.
     * @param impl the concrete implementation.
     * @return the RMI registry.
     * @throws RemoteException error exporting object.
     */
    public static Registry exportFssClient(FssClient impl) throws RemoteException {
        RmiId id = impl.getId();
        int port = id.getPort();
        String name = id.getName();
        RemoteClient stub = (RemoteClient) UnicastRemoteObject.exportObject(impl, port);
        Registry registry = LocateRegistry.createRegistry(port);
        try {            
            try {
                UnicastRemoteObject.unexportObject(stub, true);
            } catch (NoSuchObjectException oe) {
                // ignore
            }
            registry.bind(name, stub);
        } catch (AlreadyBoundException e) {
            registry.rebind(name, stub);
            logger.info("A rebind took place on remote object " + id);
        } 
        return registry;
    }
    public static Registry exportFssCommandLineClient(FssCommandLineClient impl) throws RemoteException {
        RmiId id = impl.getId();
        int port = id.getPort();
        String name = id.getName();
        RemoteClient stub = (RemoteClient) UnicastRemoteObject.exportObject(impl, port);
        Registry registry = LocateRegistry.createRegistry(port);
        try {            
            try {
                UnicastRemoteObject.unexportObject(stub, true);
            } catch (NoSuchObjectException oe) {
                // ignore
            }
            registry.bind(name, stub);
        } catch (AlreadyBoundException e) {
            registry.rebind(name, stub);
            logger.info("A rebind took place on remote object " + id);
        } 
        return registry;
    }

    /**
     * Export an <code>FssClient</code> object to RMI.
     * @param impl the concrete implementation.
     * @return the RMI registry.
     * @throws RemoteException error exporting object.
     */
    public static Registry exportFssClient(ClientUIModel impl) throws RemoteException {
        RmiId id = impl.getId();
        int port = id.getPort();
        String name = id.getName();
        RemoteClient stub = (RemoteClient) UnicastRemoteObject.exportObject(impl, port);
        Registry registry = LocateRegistry.createRegistry(port);
        try {
            try {
                UnicastRemoteObject.unexportObject(stub, true);
            } catch (NoSuchObjectException oe) {
                // ignore
            }
            registry.bind(name, stub);
        } catch (AlreadyBoundException e) {
            registry.rebind(name, stub);
            logger.info("A rebind took place on remote object " + id);
        }
        return registry;
    }
}
